Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Checks: semantics validation on attribute reference #163

Closed

Conversation

magodo
Copy link
Contributor

@magodo magodo commented Apr 12, 2020

This is similar to S035-S037, except rather than syntax checks, it applies a semantics validation. The terraform acc test could do some semantics validation on attribute reference, while it failed to test for other cases like referring to attribute to multi nested blocks.

For example, the following is the explanation of the S038 implemented in this PR:

S038

The S038 analyzer reports cases of Schemas which include ConflictsWith and have invalid schema attribute references.

NOTE: This pass only works with Terraform resources that are fully defined in a single function.

Flagged Code

// Attribute reference in multi nested block is not supported
&schema.Resource{
    Schema: map[string]*schema.Schema{
        "x": {
            Type: schema.TypeList,
            Elem: &schema.Resource{
                Schema: map[string]*schema.Schema{
                    "foo": {
                        AtLeastOneOf: []string{"x.0.bar"},
                    },
                    "bar": {
                        AtLeastOneOf: []string{"x.0.foo"},
                    },
                },
            },
        },
    },
}

or

// Non-existed attribute reference
&schema.Resource{
    Schema: map[string]*schema.Schema{
        "x": {
            Type: schema.TypeList,
            MaxItems: 1,
            Elem: &schema.Resource{
                Schema: map[string]*schema.Schema{
                    "foo": {
                        AtLeastOneOf: []string{"x.1.bar"},
                    },
                    "bar": {
                        AtLeastOneOf: []string{"x.1.foo"},
                    },
                },
            },
        },
    },
}

Passing Code

&schema.Resource{
    Schema: map[string]*schema.Schema{
        "x": {
            Type: schema.TypeList,
            MaxItems: 1,
            Elem: &schema.Resource{
                Schema: map[string]*schema.Schema{
                    "foo": {
                        AtLeastOneOf: []string{"x.0.bar"},
                    },
                    "bar": {
                        AtLeastOneOf: []string{"x.0.foo"},
                    },
                },
            },
        },
    },
}

Ignoring Reports

Singular reports can be ignored by adding the a //lintignore:S038 Go code comment at the end of the offending line or on the line immediately proceding, e.g.

//lintignore:S038
&schema.Resource{
    // ...
}

@magodo magodo force-pushed the attribute_reference_semantics_validation branch from bfc47a3 to 2a7b858 Compare April 14, 2020 03:19
@magodo magodo marked this pull request as ready for review April 14, 2020 03:20
Copy link
Owner

@bflad bflad left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @magodo 👋 Thanks for submitting this. Please see the below initial feedback and let me know if you have any questions.

@@ -0,0 +1,86 @@
# S038

The S038 analyzer reports cases of Schemas which include `ConflictsWith` and have invalid schema attribute references.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
The S038 analyzer reports cases of Schemas which include `ConflictsWith` and have invalid schema attribute references.
The S038 analyzer reports cases of Schemas which include `AtLeastOneOf` and have invalid schema attribute references.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the mistake 😅

Comment on lines 84 to 88
curSchema := curSchemaOrResource.(*tfschema.Schema)
if curSchema.MaxItems != 1 || curSchema.Type != tfschema.TypeList {
return nil, fmt.Errorf("%q configuration block attribute references are only valid for TypeList and MaxItems: 1 attributes", attributeReference)
}
curSchemaOrResource = curSchema.Elem
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please introduce type assertion safety here with a switch statement utilizing .(type):

Suggested change
curSchema := curSchemaOrResource.(*tfschema.Schema)
if curSchema.MaxItems != 1 || curSchema.Type != tfschema.TypeList {
return nil, fmt.Errorf("%q configuration block attribute references are only valid for TypeList and MaxItems: 1 attributes", attributeReference)
}
curSchemaOrResource = curSchema.Elem
switch current := curSchemaOrResource.(type) {
case *tfschema.Schema:
if current.MaxItems != 1 || current.Type != tfschema.TypeList {
return nil, fmt.Errorf("%q configuration block attribute references are only valid for TypeList and MaxItems: 1 attributes", attributeReference)
}
curSchemaOrResource = current.Elem
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Also for the default case (which means we are not capable to deal with for now), I think we shall just return nil to avoid false positive.

Comment on lines 90 to 95
// For even part, ensure it references to defined attribute
schema := curSchemaOrResource.(*tfschema.Resource).Schema[attributeReferencePart]
if schema == nil {
return nil, fmt.Errorf("%q references to unknown attribute", attributeReference)
}
curSchemaOrResource = schema
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same here. We will want to be careful for unexpected types and missing key references:

Suggested change
// For even part, ensure it references to defined attribute
schema := curSchemaOrResource.(*tfschema.Resource).Schema[attributeReferencePart]
if schema == nil {
return nil, fmt.Errorf("%q references to unknown attribute", attributeReference)
}
curSchemaOrResource = schema
// For even part, ensure it references to defined attribute
switch current := curSchemaOrResource.(type) {
case *tfschema.Resource:
if current.Schema == nil {
return nil, fmt.Errorf("%q references attribute without schema")
}
schema, ok := current.Schema[attributeReferencePart]
if !ok || schema == nil {
return nil, fmt.Errorf("%q references to unknown attribute", attributeReference)
}
curSchemaOrResource = schema
}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A little improvement of this might be just using schema := current.Schema[attributeReferencePart] instead, and in absent key case, it will return the zero value of the Value, in which case it is nil (since we do not differ between absent key and nil value).


The S038 analyzer reports cases of Schemas which include `ConflictsWith` and have invalid schema attribute references.

NOTE: This pass only works with Terraform resources that are fully defined in a single function.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Terraform resources that meet the criteria for this check can come from a variety of source code, e.g. variables, inline declarations in schema.Provider.ResourcesMap, etc.

Suggested change
NOTE: This pass only works with Terraform resources that are fully defined in a single function.
NOTE: This pass only works with `schema.Resource` that are wholly declared.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right

## Flagged Code

```go
// Attribute reference in multi nested block is not supported
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Attribute reference in multi nested block is not supported
// Attribute reference in configuration block with potentially more than one element is not supported

},
},
}
// Failing
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's ensure the following additional failure cases are tested:

  • TypeSet
  • Root attribute referencing non-existing root attribute
  • Root attribute referencing existing configuration block attribute with non-existing nested attribute
  • Configuration block nested attribute referencing non-existing root attribute
  • Configuration block nested attribute referencing existing configuration block attribute with non-existing nested attribute

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, and I will also add the corresponding pass test cases for those scenarios.

},
}

// Passing
Copy link
Owner

@bflad bflad Apr 14, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@bflad bflad added ast AST Handling check/schema Schema Check new-check New Lint Check labels Apr 14, 2020
1. Add more test cases for different scenarios, both pass and failure
   cases.
2. Add type assertion to avoid crash on unepxected schema declarations.
3. Modify the signature of `ValidateAttributeReference()` to only return
   the validation resutl.
@magodo
Copy link
Contributor Author

magodo commented Apr 15, 2020

@bflad Thank you for the quick response👍
I have made the changes per your comment, please take a look!

@bflad
Copy link
Owner

bflad commented Aug 5, 2020

Hi again @magodo 👋 After much thought about this and the recent release of Terraform Plugin SDK v2, which includes unit testing for AtLeastOneOf/ConflictsWith/ExactlyOneOf/RequiredWhen attribute reference semantics, I'm going to opt to close this in preference of the SDK itself providing validation using the fully built schema. With increasing inter-connection between AST nodes and the various forms they can take, attempting to reconstruct the intended source code representation from purely the Go AST will always be a challenge, and we would prefer to avoid that complexity when unit testing the assembled schema will cover all cases and potentially be more accurate. Thanks again for the contribution and sorry that we are going the other route here.

@bflad bflad closed this Aug 5, 2020
@magodo
Copy link
Contributor Author

magodo commented Aug 6, 2020

@bflad No worry 🙃 Also thanks for the unit testing news in the new SDK!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ast AST Handling check/schema Schema Check new-check New Lint Check
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants